{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# Vigenère cipher\n",
    "Vigenere encryption, decryption and ciphertext-only attack in python. [@gjs990825](https://github.com/gjs990825)\n",
    "Check [Vigenère cipher](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) on Wikipedia for more information."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "___\n",
    "## Part 1. Encryption and decryption"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### The code"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from dataclasses import dataclass\n",
    "from itertools import cycle, starmap\n",
    "\n",
    "# constants\n",
    "A = ord('A')\n",
    "MAX_KEY_LENGTH = 50\n",
    "MAX_KEY_CANDIDATE = 10\n",
    "MAX_DUPLICATED_PART = 0.7\n",
    "\n",
    "# frequency taken from https://en.wikipedia.org/wiki/Letter_frequency\n",
    "FREQ_ENGLISH = [0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228,\n",
    "                0.02015, 0.06094, 0.06966, 0.00153, 0.00772, 0.04025,\n",
    "                0.02406, 0.06749, 0.07507, 0.01929, 0.00095, 0.05987,\n",
    "                0.06327, 0.09056, 0.02758, 0.00978, 0.0236, 0.0015,\n",
    "                0.01974, 0.00074]\n",
    "# IC(index of coincidence) expected for english\n",
    "IC_ENGLISH = sum(f * f for f in FREQ_ENGLISH) * 26\n",
    "\n",
    "\n",
    "def alpha_only(text):  # -> str:\n",
    "    \"\"\" get all capitalized alpha only text \"\"\"\n",
    "    return ''.join(filter(lambda c: c.isalpha(), text)).upper()\n",
    "\n",
    "\n",
    "class Vigenere:\n",
    "    def __init__(self, keyword: str):\n",
    "        self.keyword = alpha_only(keyword)\n",
    "\n",
    "    @staticmethod\n",
    "    def get_cipher(p, k):  # -> str:\n",
    "        \"\"\" encrypt character p using character k as key \"\"\"\n",
    "        return chr(A + ((ord(p) - A) + (ord(k) - A)) % 26)\n",
    "\n",
    "    @staticmethod\n",
    "    def get_plain(c, k):  # -> str:\n",
    "        \"\"\" decrypt character c using character k \"\"\"\n",
    "        return chr(A + ((ord(c) - A) - (ord(k) - A)) % 26)\n",
    "\n",
    "    @staticmethod\n",
    "    def extract_extra(text):\n",
    "        \"\"\" extract spaces and other non-alpha character's positional information \"\"\"\n",
    "        return list(filter(lambda x: not x[1].isalpha(), enumerate(text)))\n",
    "\n",
    "    @staticmethod\n",
    "    def add_extra(text, extra):\n",
    "        text = list(text)\n",
    "        for e in extra:\n",
    "            text.insert(*e)\n",
    "        return ''.join(text)\n",
    "\n",
    "    def encrypt(self, plain_text, keep_extra=False):  # -> str:\n",
    "        extra = self.extract_extra(plain_text) if keep_extra else []\n",
    "        plain_text = alpha_only(plain_text)\n",
    "        cipher_text = ''.join(starmap(self.get_cipher, zip(plain_text, cycle(self.keyword))))\n",
    "        return self.add_extra(cipher_text, extra) if extra else cipher_text\n",
    "\n",
    "    def decrypt(self, cipher_text, keep_extra=False):  # -> str:\n",
    "        extra = self.extract_extra(cipher_text) if keep_extra else []\n",
    "        cipher_text = alpha_only(cipher_text)\n",
    "        plain_text = ''.join(starmap(self.get_plain, zip(cipher_text, cycle(self.keyword))))\n",
    "        return self.add_extra(plain_text, extra) if extra else plain_text\n",
    "\n",
    "    def encrypt_file(self, in_path, out_path, keep_extra=True):\n",
    "        with open(in_path, 'r') as in_file, open(out_path, 'w') as out_file:\n",
    "            cipher_text = self.encrypt(in_file.read(), keep_extra)\n",
    "            out_file.write(cipher_text)\n",
    "\n",
    "    def decrypt_file(self, in_path, out_path, keep_extra=True):\n",
    "        with open(in_path, 'r') as in_file, open(out_path, 'w') as out_file:\n",
    "            plain_text = self.decrypt(in_file.read(), keep_extra)\n",
    "            out_file.write(plain_text)"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "### Load test example"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Differential Privacy is the state-of-the-art goal for the problem of privacy-preserving data release and privacy-preserving data mining. Existing techniques using differential privacy, however, cannot effectively handle the publication of high-dimensional data. In particular, when the input dataset contains a large number of attributes, existing methods incur higher computing complexity and lower information to noise ratio, which renders the published data next to useless. This proposal aims to reduce computing complexity and signal to noise ratio. The starting point is to approximate the full distribution of high-dimensional dataset with a set of low-dimensional marginal distributions via optimizing score function and reducing sensitivity, in which generation of noisy conditional distributions with differential privacy is computed in a set of low-dimensional subspaces, and then, the sample tuples from the noisy approximation distribution are used to generate and release the synthetic dataset. Some crucial science problems would be investigated below: (i) constructing a low k-degree Bayesian network over the high-dimensional dataset via exponential mechanism in differential privacy, where the score function is optimized to reduce the sensitivity using mutual information, equivalence classes in maximum joint distribution and dynamic programming; (ii)studying the algorithm to compute a set of noisy conditional distributions from joint distributions in the subspace of Bayesian network, via the Laplace mechanism of differential privacy. (iii)exploring how to generate synthetic data from the differentially private Bayesian network and conditional distributions, without explicitly materializing the noisy global distribution. The proposed solution may have theoretical and technical significance for synthetic data generation with differential privacy on business prospects.\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    with open('original.txt', 'r') as f:\n",
    "        original_text = f.read()\n",
    "except FileNotFoundError as e:\n",
    "    print(e)\n",
    "    original_text = ''\n",
    "\n",
    "print(original_text)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### Encryption and decryption test"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [
    {
     "data": {
      "text/plain": "('LVKTWVGVGNODTTQIFQQMUBUJGLEVMBKHZICZGLCSPHWEYVWTTWOQSESHXENJSGAXEJGWVXQALRSXCZRQSSWGIAIDJMXIPDDJIUMEAWFKFIGFAARKVTJLAWVQALHWGJVVVIWWWAVSUVMHNRWSFXKIYUFAZCKLMCOIXMEHOFRQBRKTWGVQIJZQLCVQQSLLGXHGZAGCBVTBGJJQTMRAQGVFNCFENLNYOARRIEYWUYNIEBVWRVPRNBHYVLNYOKIVKBSHSMPANQOJKGVHRPWVQNNYHJMDCGJGWBKAGNBYQGBUTRKMPKHWVAKJMEHCETWBVSUUSOXYJLAXAIAIZGAGZVSTGVOIGNCFXQVBNGWVCBVTKZMEPEJBVITAGMSHYDTVXVWHFIGFBWBVBBZGWPGAFYVAWRZBUCKENIVRGLSTMQZQWGQUCZHARIKBRDDIZQGDOFHUQTSODXQVBNGWVCBVTHZIUBNWHARIXBNBLMUBBFDHVQFVROLIVPRKIDPFQFYFAFWBVTBGJJQTMRAQGVFNCFENLNYOKIVEVYVSWGBBKZGAFQZJBKMQVNQASVIQAFZVMUBENPMXKWAXJAEQXGNAADKVTXQGVGNHSQLMQVNSRJIFCPNBYWGVFNHAZKBLNBOLKKULSFITIGNCFSHVBNGQGQVQNHASPIYIWKXTQOZHASPAJNHZHKNSJFWRVQNQDJMXIPDWKGQUCZHWHKVNXSLSHTBBRAQGVFNCFENAHGGHEEMFFBVXJMAYVWWCUCQSLYRTRXTJSOBUJBGMUGNUDJSZQZFHASPLVXHJMDCGNCFETMHXSVXQORSSJEVMNSRJINMNXSLLGALSHZIVQPIOLEUMGXCEIEZHHWSPUKVJBUIRZBGZWQUEBZZVFGQAASKXKONYSVFGTBBWUSPAGWIUXKVTFZGAMLRLFWIDILJGAEPVRYKGVMWIJFLLGPVLVVMOMAXWGRCTQFHSWGBINOWBRWAJBLMCTZJQZEPQFRWFHKNSJFWRVQNQDJMXIPDKZITMGMSKGQZRKIFGVQBSWKSRBVRWRIFBBWSVYEMGMSKIPAVYWNMVGHXWFKOCGZODMPNBWASXKWAJEMMXIYJBUIETNXGWWKVZFLAQWUWTWFXFQFYFAFWBVTBSRFLLSOEMEXETUJEOUVSUAMUBHIMARIBUJODKQZVYVEXQKBRDMXGIFJHGJPWVXMUSPLVYWGRCTQNGLVKJHYWGRUNETABSKVGIWKXTQOZHASPAVSHZIUCOXDSGGWSGOQIUQNSBWXYWEPJAEVPRQOHPCKRRSULCVVXAGJFQSKSJIPBVFZHVKDNHMAMKMKUZGVKVTMCOXQORSSJEVMFDBLLGBVHRSXCNETALLGLVKTWVGVGNODPAXENJSXGJNDSKMCVAJHOSTSNSRUSPLVYWGRCTQNGLVKJHYWGRUEVYVGYVMKUZAGKBYDASXGZVFZADKVTYVWRQQFDUDSDIYIWKXTQOZHASPBUJDJSRWFJRKSNCGNCFQCGUFJWXJMBWSLMEIYFBVXGKUSWUENAVLBAJKKNSQWJQZFDBLLGBVHRSXCORSSJEVQBSKAXJLVKTWVGVGNODTTQIFQQSPJHXWFIUACWCKTGKGX',\n 'DIFFERENTIALPRIVACYISTHESTATEOFTHEARTGOALFORTHEPROBLEMOFPRIVACYPRESERVINGDATARELEASEANDPRIVACYPRESERVINGDATAMININGEXISTINGTECHNIQUESUSINGDIFFERENTIALPRIVACYHOWEVERCANNOTEFFECTIVELYHANDLETHEPUBLICATIONOFHIGHDIMENSIONALDATAINPARTICULARWHENTHEINPUTDATASETCONTAINSALARGENUMBEROFATTRIBUTESEXISTINGMETHODSINCURHIGHERCOMPUTINGCOMPLEXITYANDLOWERINFORMATIONTONOISERATIOWHICHRENDERSTHEPUBLISHEDDATANEXTTOUSELESSTHISPROPOSALAIMSTOREDUCECOMPUTINGCOMPLEXITYANDSIGNALTONOISERATIOTHESTARTINGPOINTISTOAPPROXIMATETHEFULLDISTRIBUTIONOFHIGHDIMENSIONALDATASETWITHASETOFLOWDIMENSIONALMARGINALDISTRIBUTIONSVIAOPTIMIZINGSCOREFUNCTIONANDREDUCINGSENSITIVITYINWHICHGENERATIONOFNOISYCONDITIONALDISTRIBUTIONSWITHDIFFERENTIALPRIVACYISCOMPUTEDINASETOFLOWDIMENSIONALSUBSPACESANDTHENTHESAMPLETUPLESFROMTHENOISYAPPROXIMATIONDISTRIBUTIONAREUSEDTOGENERATEANDRELEASETHESYNTHETICDATASETSOMECRUCIALSCIENCEPROBLEMSWOULDBEINVESTIGATEDBELOWICONSTRUCTINGALOWKDEGREEBAYESIANNETWORKOVERTHEHIGHDIMENSIONALDATASETVIAEXPONENTIALMECHANISMINDIFFERENTIALPRIVACYWHERETHESCOREFUNCTIONISOPTIMIZEDTOREDUCETHESENSITIVITYUSINGMUTUALINFORMATIONEQUIVALENCECLASSESINMAXIMUMJOINTDISTRIBUTIONANDDYNAMICPROGRAMMINGIISTUDYINGTHEALGORITHMTOCOMPUTEASETOFNOISYCONDITIONALDISTRIBUTIONSFROMJOINTDISTRIBUTIONSINTHESUBSPACEOFBAYESIANNETWORKVIATHELAPLACEMECHANISMOFDIFFERENTIALPRIVACYIIIEXPLORINGHOWTOGENERATESYNTHETICDATAFROMTHEDIFFERENTIALLYPRIVATEBAYESIANNETWORKANDCONDITIONALDISTRIBUTIONSWITHOUTEXPLICITLYMATERIALIZINGTHENOISYGLOBALDISTRIBUTIONTHEPROPOSEDSOLUTIONMAYHAVETHEORETICALANDTECHNICALSIGNIFICANCEFORSYNTHETICDATAGENERATIONWITHDIFFERENTIALPRIVACYONBUSINESSPROSPECTS')"
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v = Vigenere('infosec')\n",
    "# cipher_text = v.encrypt(original_text, keep_extra=True)\n",
    "cipher_text = v.encrypt(original_text)\n",
    "decrypted_text = v.decrypt(cipher_text, keep_extra=True)\n",
    "cipher_text, decrypted_text"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### File operations"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "outputs": [],
   "source": [
    "v = Vigenere('infosec')\n",
    "v.encrypt_file('original.txt', 'encrypted.txt', keep_extra=True)\n",
    "v.decrypt_file('encrypted.txt', 'decrypted.txt', keep_extra=True)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "---\n",
    "## Part 2. Ciphertext-only attack\n",
    "Based on [index of coincidence](https://en.wikipedia.org/wiki/Index_of_coincidence) technique, see [example section](https://en.wikipedia.org/wiki/Index_of_coincidence#Example) for details."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [],
   "source": [
    "def index_of_coincidence(text):  # -> float:\n",
    "    \"\"\" calculate IC(index of coincidence) of given string sequence\n",
    "    Check this for details: https://en.wikipedia.org/wiki/Index_of_coincidence\"\"\"\n",
    "    n = len(text)\n",
    "    if n <= 1:\n",
    "        return 26  # or should I raise an error here?\n",
    "    counts = [0 for _ in range(26)]\n",
    "    for c in text:\n",
    "        counts[ord(c) - A] += 1\n",
    "    return sum(c * (c - 1) for c in counts) / (n * (n - 1) / 26)\n",
    "\n",
    "\n",
    "def group_with_length(text, n):  # -> list[list[str]]:\n",
    "    \"\"\" i_th item falls into (i % length)_th group \"\"\"\n",
    "    results = [[] for _ in range(n)]\n",
    "    for i, c in enumerate(text):\n",
    "        results[i % n].append(c)\n",
    "    return results\n",
    "\n",
    "\n",
    "@dataclass(frozen=True)\n",
    "class KeyInfo:\n",
    "    length: int\n",
    "    ic: float\n",
    "\n",
    "\n",
    "@dataclass(frozen=True)\n",
    "class Key:\n",
    "    key: str\n",
    "    ic: float\n",
    "\n",
    "\n",
    "def guess_key_length(text):  # -> list[KeyInfo]:\n",
    "    \"\"\" compare AVERAGE IC of every key length in [1, MAX_KEY_LENGTH),\n",
    "     return the top MAX_KEY_CANDIDATE ones close to IC_ENGLISH \"\"\"\n",
    "    key_info = []\n",
    "    for length in range(1, min(MAX_KEY_LENGTH, len(text))):\n",
    "        substrings = group_with_length(text, length)\n",
    "        average_ic = sum(index_of_coincidence(ss) for ss in substrings) / len(substrings)\n",
    "        key_info.append(KeyInfo(length, average_ic))\n",
    "    return sorted(key_info, key=lambda x: abs(x.ic - IC_ENGLISH))[:10]\n",
    "\n",
    "\n",
    "def correlation_of(text):  # -> float:\n",
    "    \"\"\" correlation between the text letter frequencies and the relative letter frequencies for normal English text \"\"\"\n",
    "    n = len(text)\n",
    "    counts = [0] * 26\n",
    "    for c in text:\n",
    "        counts[ord(c) - A] += 1\n",
    "    return sum(counts[i] / n * FREQ_ENGLISH[i] for i in range(26))\n",
    "\n",
    "\n",
    "def get_single_key(text):  # -> str:\n",
    "    \"\"\" test every character as key for given text, use the one that has the highest correlation \"\"\"\n",
    "    correlations, max_idx = [], 0\n",
    "    for i in range(26):\n",
    "        v = Vigenere(chr(A + i))\n",
    "        correlations.append(correlation_of(v.decrypt(text)))\n",
    "        if correlations[i] > correlations[max_idx]:\n",
    "            max_idx = i\n",
    "    return chr(A + max_idx)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "outputs": [],
   "source": [
    "def crack_virginia(cipher_text, keep_extra=True, save_to=None):  # -> list[Key]:\n",
    "    extra = Vigenere.extract_extra(cipher_text) if keep_extra else []\n",
    "    cipher_text, keys = alpha_only(cipher_text), []\n",
    "\n",
    "    # try all possible key length, find their corresponding keys\n",
    "    for key_info in guess_key_length(cipher_text):\n",
    "        substrings = group_with_length(cipher_text, key_info.length)\n",
    "        key = Key(''.join(get_single_key(ss) for ss in substrings), key_info.ic)\n",
    "        print(f'Key length {key_info.length}, IC = {key_info.ic:.3f}: {key.key}')\n",
    "        keys.append(key)\n",
    "\n",
    "    # add extra information back\n",
    "    cipher_text = Vigenere.add_extra(cipher_text, extra)\n",
    "\n",
    "    # remove similar(the ones have duplicated part bigger than MAX_DUPLICATED_PART) keys\n",
    "    copy, keep = sorted(keys, key=lambda k: len(k.key)), []\n",
    "    while len(copy) > 1:\n",
    "        drop = False\n",
    "        for other in copy[:-1]:\n",
    "            original = len(copy[-1].key)\n",
    "            processed = len(copy[-1].key.replace(other.key, ''))\n",
    "            if (1 - processed / original) > MAX_DUPLICATED_PART:\n",
    "                drop = True\n",
    "                break\n",
    "        if not drop:\n",
    "            keep.insert(0, copy[-1])\n",
    "        copy.pop()\n",
    "    keep.extend(copy)\n",
    "    keys = list(filter(lambda k: k in keep, keys))\n",
    "\n",
    "    # save decoding results\n",
    "    if save_to:\n",
    "        with open(save_to, 'w') as save_file:\n",
    "            for key in keys:\n",
    "                save_file.write(f'Decrypt using {key}:\\n')\n",
    "                v = Vigenere(key.key)\n",
    "                save_file.write(v.decrypt(cipher_text, keep_extra))\n",
    "                save_file.write('\\n\\n')\n",
    "            print(f'check file {save_to} for cracking results')\n",
    "    # or print them\n",
    "    else:\n",
    "        for key in keys:\n",
    "            print(f'Decrypt using {key}:')\n",
    "            v = Vigenere(key.key)\n",
    "            print(v.decrypt(cipher_text, keep_extra), end='\\n\\n')\n",
    "    return keys"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### Breaking the example cipher"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Key length 7, IC = 1.711: INFOSEC\n",
      "Key length 28, IC = 1.713: INFOSECINFOSECINFOSECINFOSEC\n",
      "Key length 14, IC = 1.713: INFOSECINFOSEC\n",
      "Key length 21, IC = 1.684: INFOSECINFOSECINFOSEC\n",
      "Key length 35, IC = 1.676: INFOSECINFOSECINFOSECINFOSECINFOSEC\n",
      "Key length 42, IC = 1.669: INFOSECINFOSECINFOSECINFOSECINFOSECINUOSEC\n",
      "Key length 49, IC = 1.624: XNFOSECINFOSECINFOSECINFOSECINFOSECINFOSECINFOSEC\n",
      "Key length 30, IC = 1.137: SCOOSSJSCEOANRSNNICWJOSNODPXNB\n",
      "Key length 40, IC = 1.122: SCDNPSJJCRTROASOCIZQUSSESECDRXJHIIGOTOCN\n",
      "Key length 29, IC = 1.119: WDOERNOSCCRUCNJASJCOCXIODIPIN\n",
      "Decrypt using Key(key='INFOSEC', ic=1.711004490531436):\n",
      "DIFFERENTIAL PRIVACY IS THE STATE-OF-THE-ART GOAL FOR THE PROBLEM OF PRIVACY-PRESERVING DATA RELEASE AND PRIVACY-PRESERVING DATA MINING. EXISTING TECHNIQUES USING DIFFERENTIAL PRIVACY, HOWEVER, CANNOT EFFECTIVELY HANDLE THE PUBLICATION OF HIGH-DIMENSIONAL DATA. IN PARTICULAR, WHEN THE INPUT DATASET CONTAINS A LARGE NUMBER OF ATTRIBUTES, EXISTING METHODS INCUR HIGHER COMPUTING COMPLEXITY AND LOWER INFORMATION TO NOISE RATIO, WHICH RENDERS THE PUBLISHED DATA NEXT TO USELESS. THIS PROPOSAL AIMS TO REDUCE COMPUTING COMPLEXITY AND SIGNAL TO NOISE RATIO. THE STARTING POINT IS TO APPROXIMATE THE FULL DISTRIBUTION OF HIGH-DIMENSIONAL DATASET WITH A SET OF LOW-DIMENSIONAL MARGINAL DISTRIBUTIONS VIA OPTIMIZING SCORE FUNCTION AND REDUCING SENSITIVITY, IN WHICH GENERATION OF NOISY CONDITIONAL DISTRIBUTIONS WITH DIFFERENTIAL PRIVACY IS COMPUTED IN A SET OF LOW-DIMENSIONAL SUBSPACES, AND THEN, THE SAMPLE TUPLES FROM THE NOISY APPROXIMATION DISTRIBUTION ARE USED TO GENERATE AND RELEASE THE SYNTHETIC DATASET. SOME CRUCIAL SCIENCE PROBLEMS WOULD BE INVESTIGATED BELOW: (I) CONSTRUCTING A LOW K-DEGREE BAYESIAN NETWORK OVER THE HIGH-DIMENSIONAL DATASET VIA EXPONENTIAL MECHANISM IN DIFFERENTIAL PRIVACY, WHERE THE SCORE FUNCTION IS OPTIMIZED TO REDUCE THE SENSITIVITY USING MUTUAL INFORMATION, EQUIVALENCE CLASSES IN MAXIMUM JOINT DISTRIBUTION AND DYNAMIC PROGRAMMING; (II)STUDYING THE ALGORITHM TO COMPUTE A SET OF NOISY CONDITIONAL DISTRIBUTIONS FROM JOINT DISTRIBUTIONS IN THE SUBSPACE OF BAYESIAN NETWORK, VIA THE LAPLACE MECHANISM OF DIFFERENTIAL PRIVACY. (III)EXPLORING HOW TO GENERATE SYNTHETIC DATA FROM THE DIFFERENTIALLY PRIVATE BAYESIAN NETWORK AND CONDITIONAL DISTRIBUTIONS, WITHOUT EXPLICITLY MATERIALIZING THE NOISY GLOBAL DISTRIBUTION. THE PROPOSED SOLUTION MAY HAVE THEORETICAL AND TECHNICAL SIGNIFICANCE FOR SYNTHETIC DATA GENERATION WITH DIFFERENTIAL PRIVACY ON BUSINESS PROSPECTS.\n",
      "\n",
      "Decrypt using Key(key='SCOOSSJSCEOANRSNNICWJOSNODPXNB', ic=1.1365478686233401):\n",
      "TTWFEDXDEJAD GCYVSIO QL NCW SIPYZ-AS-FLU-KHX TAOB HJN GIJ LRAFCAR EE IHAIAEM-JMRXETTCAY AAKP RPUJEET UXT GWLFGBP-LRZCKAMWST NVTX YEIWDG. VILZIPIJ TIORMDTEIW JJDFH NKLEDEFRUVLL TNYLOST, DKMEJMR, QWSEIZ VBFETHNTMMP TAHCYA CTE TTGMJOPONRF XV RDFJ-CDDFDDZONME TLUA. AE AQRLOSCEUM, OHTC YCQ VZTKD TEGMGUV XKAUFENE E CWWWD GKEOET CZ VGYRKZOGWP, EOXSERSK YTNREUX LXITI DIBRKA TCRCEOIKS YJAFLVILAN HIG LSIOQ DQPSVBROAPX VU MNVTI SNEIS, SXYQX MAJTEFA TVA ULVRZOHEU RFRI OVJT NN HONXEWR. YIJE EMTSGBQV VHOR OF SUOLCE OHCAVTAER SOEVBMQCOQ ACS XDSAMP JY DSVES HCOEB. UMA SFEIPNDF IEAAT KG NJ NUPTMRVEXTV IHP OZPX SCCJINEEZHFJ OA ROPY-RNZOISFAJVZ TAKLVLI DDWH E EOS JI VSA-SZHWOCKUMZY NESTTNEH TYGJMEXKTWWNG RNR IVKEMIQWSE ADFDE ZTAYCUOR ZSE SQSPHLFP IOIRKSDMJJJ, ZN WTBSS HEFVCQTAUD WY HJASN RTIPVFMEXQP QUGJTDXHUNKNE AZPM THYVWEEPHCVY URKTUPQ FS TDMADYIP XH K IVY RP RNN-ZIHOTBZCSNV NUYELVQUS, RYG AWLI, WHI EKLKOO XYECZK GBQS SGR OSJFJ ATLHELYHWPYOB LIGPWZVAKEON RFJ SAFU FO ADAAAMTI ZSE SQAZFVW CXO NXPSCVUYN UATMLUE. TOEV NHUUOQT LWDWNRT UMAOXICC MSHXR RG DJIFXPISEKAI RDEEO: (V) CQBMOEZCVGHT S ION Z-DPPWIQ QUIUJNDX TDKSOMU UEVF YUO CIDT-ZDAUNJTRUPS YDTEEOS QLK IBEFIWODKGK LRDLBATSQ ED TWVAANUNHQAZ LWZPGTU, WHVFJ RPF JOOLD SQWOTMNS JT AEONPAIUN ON TDYLDU EYE SQGITUINZEO UKODO FOOMAA XSAAEYEJSER, RCIYXVHROHA CXEJOJI HG CSKIOIG EBNNV BCFLOISJTTXS EZS XIDRRLM VQFCRVWSREU; (NV)COUAKEIU JHV LONDYDWHQ FY BJPZYXT R NWU YH TNHFZ GPAOIXEEDOB YEOJRWJUHETEM LIKM JFWSR LJJFRCAHPRANW HS UIQ HPGVHJSO JE DZTVTYLE NEFPECL, VAR EXE DGFTTWZ EERWFIUFY SV NYJSQFUPOENM UNIHETU. (NYH)XNHYOTWHB UTW VM ARFBRRIE DHSXTTNSS UFWK LQFI TCO JRWTJEOITFMHGM FRZGDAT IVBEWUKM IHDASGB VFE MQTCHGJSONW DMOJHWRPPEENG, EIHDTLN KOLLITWYJG NRFELHNHRLIRF YIF ZDDXB YUELVK FHNKSYMLTIAG. JSF PJFAESWJ IWEOOAOC BFT TNHI JRUSEQHYEVH NOI PEOLEEHQK LYYAIHWWVAHE HML FQKTYTTTL IEFP AODVWDDONE SIOR JRWTJEOITFMH KFYVRNB VC IPVIRQCR KUYWTTTOK.\n",
      "\n",
      "Decrypt using Key(key='SCDNPSJJCRTROASOCIZQUSSESECDRXJHIIGOTOCN', ic=1.121788617886179):\n",
      "TTHGHDXMEWVM FTYUDIR WA JCF OHCSV-EB-ARA-WLN XAFX FTR JDN KRFVZEE AT VWOTYOI-TMFETEAHTD JMJJ LPDAQTT TIZ UHVERBD-LVGMNOGENG NEEC JDIDNL. DXHITDIJ EJRRMMTRDF IADES NNRTZEONTIFH XWTRUEX, TOMVFKB, UZNIHZ ARCIGDCGBAA RKDMYO JIP AYZRSAOIHRO OH GDEU-IROPCOZCNQL DOIU. IZ NQAWTDBEBW, TPIY HNE VZELG TEPMTPE WBATQEQK T YWFSC TEASNO IF HKOVKQKEGT, HJBREWIH CGJGRJL WVSPS DWIGVH YVWLQNCES HAMULUTROY RHR LGISX NTDMDWEOJAC GT MUFYQ HJNTG, SXJRA MASTRAJ SMA TWVUFDDED NEEC KZSO TT TSDBENN. WSNH ZQSSLRNZ IDDE DT DSYHME CORLCYTJA ENYPBVHODQ ZNY LOCZXP XY HZFHG BKJRB. DXF DEEPZSLU ENLOT KR OM NUYTZMEDOTU THS UOLX BYBWCJINUNLV SQ VOGU-PXDRDWEAOLW XNGAIAW OBGD O ECZ YT CXT-XITVIWKDDLN NDDZHYOG EYUJQLHNHQEIT RWC NGJETSVEHA JOTDE KUDYCDOE UBD JQRAHOLE EORNJFXINSE, FT IXRWS YADFGTOETD BO ENNOC EIWATPIOXEW FFNOMIGTTHENN RLEM SSEJHODAHQVC BBNHOXL SS CORLCYPZ CP Z EEJ FP RYO-CIHXTOULREV MFYHRKMUB, NXT USPR, RNO QOBOOF TWOGCF KAQX IDV BOYSY OEJRAVYVDEJVG ENPBVTPATVAC REU YOQE EZ ARAEHWWW TAZ FQJKKGV CEY SFEOLGIYN FBWMLDE. GJNU EHTFOTZ ASDFJQG OIEXSOIO QILXI NE NNLABOIXUHEV NSRTC: (G) AAXWOSGRGNMM X RAM T-XPYNUF QTTAXYNW EDYSSTE XSPN THO LTIE-YDHESRINDAG YDEFHOS ZLX DKDWIVZDNMZ HRMHANNOU NY ZCHEQRUEDOKD ORDOGYK, TLIBY EET UMYHN SEDDETSL OC MDIHPJZGC OM EJMWNT PYS SUNSWICVURO DVTON FVYRIP TBLOEYPKVER, ACVTGUYRNSA FDTFOSE GT WOORJOM QFDRV SYDVSLNNSTCI BDF TXQGFWK FMPCFCLDYJN; (SE)ONOUKNZG YHU WUBOICHHE FC ITSNSFO E NFF DS SNOPE OEWXTLEEOPE YEXJERSTYESPM OOZI JOSRE FFNOMIGTTHENN DQ EMT CTFVMZPS WA SMIJEWVA XETWTNS, ATW NJD XAFCKIO EDCCTTEEJ SJ NCQCTTOXJRNV FSTGEAE. (SGW)TWSMOTHIE UTF VZ VAESRQTE GNHTTCJRF OBAT GWLU XSS JISRTIRDXEMMWJ JEVVQPH TTLAGUYT XSKFLLK HEY GQCTTVJRZTK OWNUHKRTWOHBA, MDUDCWS VNLSSYENFP YFFEWIQHRUIEA HHW ZCOXE EJALEG EUHGWHHRZUEW. NSW LHPEHNAI IBULSNKR OUH ELRE TRIZTBODXAQ ZNC JEXCQTHPV RMJKHSWKVEOO KAF NLXTHEYEK ILPU IDZEHRDOYF VIOA JERCIVOHEFPN ZBYENMO PY MYQOXCGH OUPSRDXRF.\n",
      "\n",
      "Decrypt using Key(key='WDOERNOSCCRUCNJASJCOCXIODIPIN', ic=1.1186843807533462):\n",
      "PSWPFISDELXJ RGHINHO YS EMV DDPNZ-FH-TVR-PLO JABV FJV YDN RFURIEB KS PRRGECJ-KQREUEDONC RAOA LPTKETY TSV HGYTRCP-LHGDSCNNFS WGSD DSDHNE. BYETHMJY TEEHEBTVNW ERTES YETKJHKSBHUU DSMHAIU, SABZXUV, FADUIN OCEOTZNUFID XKTOZY JYX XPXXMEFTHQT RB WUQY-NJDSDDLLLCA CNGF. YD PLWKTMJFVI, YHSA IBZ LZQET YEYWBGH IEKTPEAS A ULVGP ITZNUE WL APHRDBOEMY, IYCLYAFV CCKHFZI KYQFJ MASAKQ FFWFTTGKH YPATHWXIVY RGG MXAOQ TERJNAFYYUS BN HXWTI DAZEZ, IMDEX VHNTLLM DED ZLHQHTEJT NGEO HUOM BJ QEINJSR. VNLO EDYGYTRZ QTPP RQ GDQHHU SOXULESCA XFOPZRMCOB MON SDKSWU VC TEFST NNTIX. ELE DOZEFYAO VOEBT DS NZ IVTSIQNESIU RYE WQBN OWDLWANNZHRE YV GIEE-EENSROAONCL UTWBBID VTKT V OSY TV RTE-CCVSOWUOTWW YFMIYRDL TPMNBFAEKOTMT SNQ YVEWGYQBVB OOSTJ FTPIWEDZ KEN SVRKNLKE UTMFVYYLIED, ZY GWCXY IEBRGUOLAO YF ISNOH ECTTFTXKAAL MTWTCDAHFYBVY WEHH YIZQMXIONBFD HGYTRCP EI EZAAMYWP BT Z VVD EE LMT-EENSROAONCL JNETYEMDD, RZY PVJS, JNJ AZGYZF XGPRAD RWJO JLH NEPMS KMOBFDNLBQNEX JTGNHZUCOEAR CWE TUKG PD SOEOSRHU LQA PGADNFJ JXE DDEERTNDT FAHNHYO. VANO CMYHEJN GIYBNRA CROKWIMD RNHXT OM ONRSSOIALBKH CYETO: (A) RELJTIQSVTBR S QGI D-JDJIOU AAWBTEBB RALWOTK FOHS CLO GTXT-YEAJSIOTVZF MOUEEEZ RTM JSRERHNJPUF WBBRRTNRN FS TSLQSLUEMQVH BVKAABA, CKAGQ DYO TTCHP IRLEIHBA NI EPENDTJTX OF TERHRY OKQ TONNMYEEKHE KPICC ZUTDLP IYANEYQGQUN, AEUDVUWMTGF WEFKKTI GE MRTYOFA UGNFF WORWISRTTGLO WOR HUFAMKC GKRHAEWLTES; (DE)GYZTENVF NQS BPSOXEETR OQ SSPPKAY U CBS YW TTHTV HEXJTHCEETT YEEXTNBTVORJH RBFW KFWDE GFQVGHOHYYEND NE ERT MPSUPOPT IA EMZOSDES JNVKUHH, VXW GHE ULTLLXD ZQSUITIOA OA DCQNKVFHMNSD EHGMATU. (YKT)SIHQGDBTF KFG JN GCKFNBHI OQNTJEKBF EJXK ECFY ODS INVLJZDHCWBPXY VNTHFOG REBEIPUH XBSGFXP ZOA HEXJTHCEETT YEEXTNBTVORJH, ISKRPLH UISIGEXSYL RQJECNRWSOCIX VHS ADCNB SMYBVP IEBVFORRTXKA. THN AVOAJRRP IBTATECN HAS SIBI UBXTJWIYARL RJT VPQSFNUME YHJESVHCYKDA GCV OQNTJEKBF EJXK FPEQMWHNTD CNBG XRTGIDETPTMQ KTYZDCO VH VEPHXVYX OSLXFOIEG.\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": "[Key(key='INFOSEC', ic=1.711004490531436),\n Key(key='SCOOSSJSCEOANRSNNICWJOSNODPXNB', ic=1.1365478686233401),\n Key(key='SCDNPSJJCRTROASOCIZQUSSESECDRXJHIIGOTOCN', ic=1.121788617886179),\n Key(key='WDOERNOSCCRUCNJASJCOCXIODIPIN', ic=1.1186843807533462)]"
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cipher_text = Vigenere('infosec').encrypt(original_text, keep_extra=True)\n",
    "crack_virginia(cipher_text, keep_extra=True)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### Break from file"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Key length 16, IC = 1.730: MAVERICKMAVERICK\n",
      "Key length 40, IC = 1.731: MAVERICKMAVERICKMAVIRICKMAVERICKMAVERICK\n",
      "Key length 8, IC = 1.739: MAVERICK\n",
      "Key length 24, IC = 1.740: MAVERICKMAVERICKMAVERICK\n",
      "Key length 48, IC = 1.745: MAVERICKMAVERICKMAVERICKMAVERICKMAVERICKMAVERICK\n",
      "Key length 32, IC = 1.760: MAVERICKMAVERICKMAVERICKBAVERICK\n",
      "Key length 36, IC = 1.368: MICKRMVKRAVKREGUCICERNCKRWCEREVDRAGK\n",
      "Key length 20, IC = 1.345: RAVKRICZRIVERICERIVK\n",
      "Key length 28, IC = 1.343: RIWTMIVKRAVERIRERIRKRICKRAVK\n",
      "Key length 12, IC = 1.341: RICKRIVERIVK\n",
      "check file breaking_results.txt for cracking results\n"
     ]
    }
   ],
   "source": [
    "def crack_virginia_from_file(in_path, out_path):\n",
    "    with open(in_path, 'r') as in_file:\n",
    "        crack_virginia(in_file.read(), save_to=out_path)\n",
    "\n",
    "\n",
    "crack_virginia_from_file('cipher_to_break.txt', 'breaking_results.txt')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### brute force attack"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "outputs": [],
   "source": [
    "from string import ascii_uppercase\n",
    "from itertools import product\n",
    "def brute_force_vigenere(original_text, cipher_text):\n",
    "    for length in range(len(cipher_text)):\n",
    "        for key in product(ascii_uppercase, repeat=length):\n",
    "            v = Vigenere(''.join(key))\n",
    "            if v.decrypt(cipher_text) == original_text:\n",
    "                # print(f\"The keys is:{''.join(key)}\\nThe decrypted text is:\\n{v.decrypt(cipher_text)}\")\n",
    "                return 1\n",
    "    return None\n",
    "with open('ori.txt', 'r') as f:\n",
    "    v = Vigenere('info')\n",
    "    original_text = f.read()\n",
    "    original_text = alpha_only(original_text)\n",
    "    cipher_text = v.encrypt(original_text)\n",
    "    brute_force_vigenere(original_text,cipher_text)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 测试时间"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.00016671021779378256\n",
      "0.0033726056416829428\n",
      "0.0943511168162028\n",
      "2.3974599043528237\n",
      "62.997820631663004\n"
     ]
    },
    {
     "data": {
      "text/plain": "[0.00016671021779378256,\n 0.0033726056416829428,\n 0.0943511168162028,\n 2.3974599043528237,\n 62.997820631663004]"
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import secrets\n",
    "import string\n",
    "from time import time\n",
    "def generate_string(length):\n",
    "    alphabet = string.ascii_letters\n",
    "    random_string = ''.join(secrets.choice(alphabet) for _ in range(length))\n",
    "    return random_string\n",
    "avg_sec = []\n",
    "with open('ori.txt', 'r') as f:\n",
    "    original_text = f.read()\n",
    "    original_text = alpha_only(original_text)\n",
    "    for k_length in range(1,6):\n",
    "        begin = time()\n",
    "        for times in range(1,31):\n",
    "            keys = generate_string(k_length)\n",
    "            v = Vigenere(keys)\n",
    "            cipher_text = v.encrypt(original_text)\n",
    "            brute_force_vigenere(original_text,cipher_text)\n",
    "        end = time()\n",
    "        avg_sec.append((end-begin)/30)\n",
    "        print((end-begin)/30)\n",
    "avg_sec"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 432x288 with 1 Axes>",
      "image/png": "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\n"
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def plot_line_chart(data):\n",
    "    x = range(1, len(data) + 1)\n",
    "    y = data\n",
    "    plt.plot(x, y, marker='o')\n",
    "    plt.xlabel('key_length')\n",
    "    plt.ylabel('seconds')\n",
    "    plt.xticks(x)\n",
    "    plt.title('brute force attack')\n",
    "    for i, j in zip(x, y):\n",
    "        value = '{:.2g}'.format(j)\n",
    "        plt.annotate(value, xy=(i, j), xytext=(5, 5), textcoords='offset points', ha='center')\n",
    "        plt.savefig('brute force attack.jpg',dpi=300)\n",
    "    plt.show()\n",
    "\n",
    "plot_line_chart(avg_sec)\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}